package middle_man;


import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;

public class SmartQueue {
	int P;
	int L;
	int M;
	String []db_hosts;
	String db_database;
	
	int roundRobinNumber;
	Object roundRobinGuard;
	// reason for LinkedBlockingQueue:
	//   http://stackoverflow.com/questions/1426754/linkedblockingqueue-vs-concurrentlinkedqueue
	LinkedBlockingQueue<Request> []Q;
	Worker []workers;
	
	@SuppressWarnings("unchecked")
	public SmartQueue(int _P, int _L, int _M, String []_db_hosts, String _db_database) {
		
		P = _P;
		L = _L;
		M = _M;
		db_hosts = _db_hosts.clone();
		db_database = _db_database;
		System.err.println("SmartQueue: Init with P:"+P+", L:"+L+", M:"+M +" and "+ db_hosts.length + " databases");
		Q = (LinkedBlockingQueue<Request> [])new LinkedBlockingQueue[P];
		for(int i=0; i<P; i++) {
			Q[i] = new LinkedBlockingQueue<Request>(L);
		}
		roundRobinNumber = 0;
		roundRobinGuard = new Object();
		workers = new Worker[M];
		int currentQ = 0;
		int currentDB = 0;
		assert (P == db_hosts.length);
		for(int i=0; i<M; i++) {
			workers[i] = new Worker(Q[currentQ], db_hosts[currentDB], db_database);
			assert (currentQ == currentDB);
			currentQ = (currentQ+1) % P;
			currentDB = (currentDB+1) % db_hosts.length;
		}
	}
	
	public void startWorkerThreads() {
		for(int i=0; i<M; i++) {
			workers[i].start();
		}
	}
	
	public void terminate() {
		// Kill the worker threads
		for(int i=0; i<M; i++) {
			workers[i].terminate();
		}
				
		for(int i=0; i<M; i++) {
			try {
				System.err.println("Waiting for Worker " + i + " to join");
				workers[i].join();
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	public LinkedBlockingQueue<Request> getRoundRobinQueue() {
		int result;
		synchronized (roundRobinGuard) {
			result = roundRobinNumber;
			roundRobinNumber = (roundRobinNumber + 1) % P;
		}
		return Q[result];
	}
	
	public LinkedBlockingQueue<Request> getRandomQueue() {
		int result = new Random(System.currentTimeMillis()).nextInt(P);
		return Q[result];
	}
	
}
